package com.ada.mvcframework.servlet;

import com.ada.mvcframework.annotations.*;
import com.ada.mvcframework.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;

public class LgDispatcherServlet extends HttpServlet {

    //配置文件读取
    private Properties properties = new Properties();
    //缓存扫描到类的全限定类名
    private List<String> classNames = new ArrayList<>();

    //IOC容器
    private HashMap<String, Object> ioc = new HashMap<>();

    //url和method的映射
    //private HashMap<String,Method> handlerMapping = new HashMap<>();
    private List<Handler> handlerMapping=new ArrayList<>();
    /**
     * 加载配置文件 springmvc.properties
     * 扫描注解，相关的类
     * 初始化bean对象，实现一个ioc容器，基于注解
     * 实现依赖注入
     * 构造一个handlerMapping处理器映射器，将配置好的url与method建立映射关系
     * 等待请求进入
     *
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        //servlet初始化参数
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        //1.加载配置文件 springmvc.properties
        doLoadConfig(contextConfigLocation);
        //2.扫描注解，相关的类
        doScan(properties.getProperty("scanPackage"));
        //3.初始化bean对象，实现一个ioc容器，基于注解
        doInstance();
        //4.实现依赖注入
        doAutoWired();
        //5.构造一个handlerMapping处理器映射器，将配置好的url与method建立映射关系
        initHandlerMapping();

        System.out.println("初始化完成！！！");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        Handler handler=getHandler(req);
        if(handler==null){
            resp.getWriter().write("404 not found!");
            return;
        }
        //参数绑定
        //获取所有参数类型数组，这个数组的长度就是我们要传入的args的长度
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        //创建一个新数组用于反射调用
        Object[] paraValues = new Object[parameterTypes.length];
        //像参数数组中塞进值，保证参数顺序，和方法中形参顺序一致
        Map<String, String[]> parameterMap = req.getParameterMap();
        //检测security
        if(!doCheckSecurity(handler,parameterMap)){
            resp.getWriter().write("security failed!");
            return;
        }
        //遍历request所有参数 (填充除了request，response之外的参数)
        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            String join = StringUtils.join(param.getValue(), ","); //如同1,2
            //如果参数和方法的参数匹配上了，填充数据
            if(!handler.getParamIndexMapping().containsKey(param.getKey())){
                continue;
            }
            //有该参数，找到索引位置，放入paraValues
            Integer index = handler.getParamIndexMapping().get(param.getKey());
            paraValues[index]=join;//把前台传过来的参数值放到对应位置
        }

        int requestIndex= handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        paraValues[requestIndex]=req;
        int response= handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        paraValues[response]=resp;

        //最终调用method
        try {
            handler.getMethod().invoke(handler.getController(),paraValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }
    /**
     * security检测
     */
    private boolean doCheckSecurity(Handler handler,  Map<String, String[]> parameterMap){
        if(handler.getSecuritys().isEmpty())return true;//为空则说明没有标记
        for (Map.Entry<String, Set<String>> entry : handler.getSecuritys().entrySet()) {
            if(!parameterMap.containsKey(entry.getKey())){
                return false;
            }
            for (String s : parameterMap.get(entry.getKey())) {
                if(entry.getValue().contains(s)){
                    return true;
                }
            }
        }
        return false;
    }
    private Handler getHandler(HttpServletRequest req) {
        if(handlerMapping.isEmpty())return null;
        String requestURI = req.getRequestURI();
        for (Handler handler : handlerMapping) {
            if(!handler.getPattern().matcher(requestURI).matches()){
                continue;
            }
            return handler;
        }
        return null;
    }

    /**
     * 构造handlerMapping
     * 最关键 ，将url与method建立关联
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()) return;
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            if (!aClass.isAnnotationPresent(MyController.class)) {
                continue;
            }
            String baseUrl = "";
            if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
                baseUrl = aClass.getAnnotation(MyRequestMapping.class).value();
            }

            Method[] methods = aClass.getMethods();
            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];
                //没有标识则不处理
                if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                    continue;
                }
                String value = method.getAnnotation(MyRequestMapping.class).value();
                String methodUrl = baseUrl + value; // /demo/query

                //把method与url封装为handler
                Handler handler=new Handler(entry.getValue(),method, Pattern.compile(methodUrl));
                //处理参数位置信息
                Parameter[] parameters = method.getParameters();
                for (int j = 0; j < parameters.length; j++) {
                    Parameter parameter = parameters[j];
                    if(parameter.getType()==HttpServletRequest.class||parameter.getType()==HttpServletResponse.class){
                        handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),j);
                    }else {
                        handler.getParamIndexMapping().put(parameter.getName(),j);
                    }
                }

                if(aClass.isAnnotationPresent(Security.class) ){
                    String[] usernames = aClass.getAnnotation(Security.class).value();
                    doAddHandlerSecurity(handler,usernames);
                }
                if(method.isAnnotationPresent(Security.class)){
                    String[] usernames = method.getAnnotation(Security.class).value();
                    doAddHandlerSecurity(handler,usernames);
                }
                handlerMapping.add(handler);
            }
        }
    }

    /**
     * 添加需要验证的username
     * @param handler
     * @param usernames
     */
    private void doAddHandlerSecurity(Handler handler,String[] usernames){
        for (String username : usernames) {
            if(handler.getSecuritys().containsKey("username")){
                Set<String> nameSet=handler.getSecuritys().get("username");
                nameSet.add(username);
                handler.getSecuritys().put("username",nameSet);
            }else {
                HashSet<String> nameSet=new HashSet<>();
                nameSet.add(username);
                handler.getSecuritys().put("username",nameSet);
            }
        }
    }
    /**
     * 实现依赖注入
     */
    private void doAutoWired() {
        if (ioc.isEmpty()) return;
        //有对象再进行依赖注入处理
        //遍历所有对象，查看对象的字段的注解
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //获取bean的字段
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            for (int i = 0; i < declaredFields.length; i++) {
                Field declaredField = declaredFields[i];
                if (!declaredField.isAnnotationPresent(MyAutowired.class)) {
                    continue;
                }
                MyAutowired annotation = declaredField.getAnnotation(MyAutowired.class);
                String beanName = annotation.value();
                if ("".equals(beanName)) {
                    beanName = declaredField.getType().getName();
                }
                //赋值
                declaredField.setAccessible(true);
                try {
                    declaredField.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 实现容器
     * 基于classNames的全限定类名，
     * 用反射技术实现对象创建和管理
     */
    private void doInstance() {
        if (classNames.size() == 0) {
            return;
        }
        try {
            for (int i = 0; i < classNames.size(); i++) {
                String classname = classNames.get(i);
                //反射
                Class<?> aClass = Class.forName(classname);
                //区分controller ,service
                if (aClass.isAnnotationPresent(MyController.class)) {
                    MyController annotation = aClass.getAnnotation(MyController.class);
                    String keyName = null;
                    keyName = annotation.value();
                    if ("".equals(keyName.trim())) {
                        //controller不做value
                        String simpleName = aClass.getSimpleName();
                        keyName = lowFirst(simpleName);
                    }
                    //实例化
                    Object o = aClass.newInstance();
                    ioc.put(keyName, o);
                } else if (aClass.isAnnotationPresent(MyService.class)) {
                    MyService annotation = aClass.getAnnotation(MyService.class);
                    //获取设置的value
                    String beanName = annotation.value();
                    //指定id
                    if (!"".equals(beanName.trim())) {
                        ioc.put(beanName, aClass.newInstance());
                    } else {
                        String simpleName = aClass.getSimpleName();
                        ioc.put(lowFirst(simpleName), aClass.newInstance());
                    }

                    //server层一般取接口，再以接口名作为id放入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (int i1 = 0; i1 < interfaces.length; i1++) {
                        Class<?> anInterface = interfaces[i1];
                        ioc.put(lowFirst(anInterface.getName()), aClass.newInstance());
                    }
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public String lowFirst(String s) {
        char[] chars = s.toCharArray();
        if (chars[0] >= 'A' && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }

    /**
     * 扫描注解，相关的类
     * package->file ,递归读取
     *
     * @param scanPackage
     */
    private void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replace(".", "/");
        File pack = new File(scanPackagePath);
        for (File file : pack.listFiles()) {
            if (file.isDirectory()) { //子包
                //递归
                doScan(scanPackage + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                //得到类的全限定类名
                String className = scanPackage + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }

    }

    /**
     * 加载配置文件
     * 输入流读取文件
     *
     * @param contextConfigLocation
     */
    private void doLoadConfig(String contextConfigLocation) {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}
